using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Data;
using TALoaders;
using TAAlert.CommonUtils;

namespace TAAlert.BackTest
{
    /// <summary>Encapsulation of single Strategy run. Generate DataTable with strategy figures (time series with positions, P/L , DrawDowns)</summary>
    class StrategyDetail
    {
        static private readonly TraceSwitch traceM = new TraceSwitch("StrategyDetails", "Debug info for StrategyDetails class");

        public static readonly string DATE_COL = "Date";
        public static readonly string OPEN_COL = "Open";
        public static readonly string HIGH_COL = "High";
        public static readonly string LOW_COL = "Low";
        public static readonly string CLOSE_COL = "Close";
        public static readonly string VOLUME_COL = "Volume";
        public static readonly string EMA_COL = "EMA";
        public static readonly string EMA_SHORT_COL = "EmaShort";
        public static readonly string EMA_LONG_COL = "EmaLong";
        public static readonly string EMA_1_COL = "Ema1";
        public static readonly string EMA_2_COL = "Ema2";
        public static readonly string MACD_LINE_COL = "macdLine";
        public static readonly string MACD_SIGNAL_COL = "macdSig";
        public static readonly string MACD_HISTOGRAM_COL = "macdHist";
        public static readonly string VOLATILITY_COL = "Vol";
        public static readonly string POSITION_COL = "Pos";
        public static readonly string CUMUL_PL_COL = "P/L";
        public static readonly string DRAW_DOWN_COL = "DrawDown";

        public static readonly string FORMAT_EXT_PROPERTY = "Format";

        private BaseLoader loaderM;
        private SortedList<DateTime, Quote> quotesM;
        private SortedList<DateTime, double> sigQuoteM;     // used for signal generation
        private SortedList<DateTime, double> closePriceM;   // Used for performance
        private DateTime startDateM;
        private DateTime endDateM;
        private QuoteType sigTypeM;
        private double transCostM;

        /// <summary>Constructor common for all strategies</summary>
        /// <param name="loaderName">String representaion of Loaders.Loader enum</param>
        /// <param name="ticker">Security ticker</param>
        /// <param name="sigType">QuoteType for signal generation</param>
        /// <param name="startDate">Strategy start date</param>
        /// <param name="endDate">Strategy end date</param>
        public StrategyDetail(string loaderName, string ticker, DateTime startDate, DateTime endDate, double transactionCost, QuoteType sigType)
        {
            Debug.WriteLineIf(traceM.TraceInfo, "--> StrategyDetail(" + loaderName + ", " + ticker + ", " + sigType + ", " + startDate.ToShortDateString() + ", " + endDate.ToShortDateString() + ")");
            this.loaderM = DownloaderFactory.createCachedDownloader(loaderName);
            this.startDateM = startDate;
            this.endDateM = endDate;
            this.sigTypeM = sigType;
            this.transCostM = transactionCost;

            DateTime warmUpStart = this.startDateM.AddYears(-1);
            this.quotesM = this.loaderM.getHistoricalPrices(ticker, warmUpStart, endDate);
            this.sigQuoteM = Utilities.getTimeSeries(this.quotesM, this.sigTypeM);
            this.closePriceM = Utilities.getTimeSeries(this.quotesM, QuoteType.AdjClose);
        }

         /// <summary>Generate DataTable with singel EMA strategy run</summary>
        /// <param name="nEMA">EMA period</param>
        /// <returns>DataTable with strategy time series in columns</returns>
        public DataTable generateEMATable(int nEMA)
        {
            Debug.WriteIf(traceM.TraceInfo, "generateEMATable(nEMA=" + nEMA + ") ...");
            // Create DataTable
            DataTable dTable = this.createTableColumns(new string[] { EMA_COL });

            // Calculate columns
            List<double> ema = EMAEvaluator.calcEMAArray(this.sigQuoteM.Values, nEMA);
            Strategy strategy = new EMAStrategy(nEMA, this.startDateM, this.endDateM, this.sigQuoteM);
            
            PerfResult perf = this.calculatePerformance(strategy);

            // Create rows
            for (int ix = perf.StartPriceIx; ix < this.quotesM.Count; ++ix)
            {
                // Add standard columns
                DataRow row = this.addStandardColumnsToRow(dTable, ix, perf.StartPriceIx, perf.Position, perf.CumulativePL, perf.DrawDown);
                // Add strategy specific columns
                row[EMA_COL] = ema[ix];
                dTable.Rows.Add(row);
            }
            Debug.WriteLineIf(traceM.TraceInfo, "generated " + dTable.Rows.Count + " rows and " + dTable.Columns.Count + " columns");
            return dTable;
        }

        /// <summary>Generate DataTable with single VEMA strategy run</summary>
        /// <param name="n1">EMA period for vol below volT</param>
        /// <param name="n2">EMA period for vol above volT</param>
        /// <param name="volT">Treashold volatility</param>
        /// <returns>DataTable with strategy time series in columns</returns>
        public DataTable generateVEMATable(int n1LowVol, int n2HighVol, double volT)
        {
            Debug.WriteIf(traceM.TraceInfo, "generateVEMATable(n1=" + n1LowVol + ", n2=" + n2HighVol+ ", volT=" + volT + ") ...");
            // Create DataTable and columns
            DataTable dTable = this.createTableColumns(new string[] { EMA_1_COL, EMA_2_COL, VOLATILITY_COL });

            // Calculate specific columns
            List<double> ema1 = EMAEvaluator.calcEMAArray(this.sigQuoteM.Values, n1LowVol);
            List<double> ema2 = EMAEvaluator.calcEMAArray(this.sigQuoteM.Values, n2HighVol);
            List<double> vol = EMAEvaluator.calcEMAVolatilityArray(this.sigQuoteM.Values);

            Strategy strategy = new VEMAStrategy(n1LowVol, n2HighVol, volT, this.startDateM, this.endDateM, this.sigQuoteM);
            PerfResult perf = this.calculatePerformance(strategy);

            // Create rows
            for (int ix = perf.StartPriceIx; ix < this.quotesM.Count; ++ix)
            {
                // Standard columns
                DataRow row = this.addStandardColumnsToRow(dTable, ix, perf.StartPriceIx, perf.Position, perf.CumulativePL, perf.DrawDown);
                // VEMA specific
                row[EMA_1_COL] = ema1[ix];
                row[EMA_2_COL] = ema2[ix];
                row[VOLATILITY_COL] = vol[ix];
                // add row to table
                dTable.Rows.Add(row);
            }
            Debug.WriteLineIf(traceM.TraceInfo, "generated " + dTable.Rows.Count + " rows and " + dTable.Columns.Count + " columns");
            return dTable;
        }

        /// <summary>Generate DataTable with single TwoEMA strategy run</summary>
        /// <param name="nShort">Short EMA period</param>
        /// <param name="nLong">Long EMA period</param>
        /// <param name="volTreashold">Cut-out volatility value</param>
        /// <returns>Data table with strategy time series in columns</returns>
        public DataTable generateTwoEmaTable(int nShort, int nLong, double volTreashold)
        {
            Debug.WriteIf(traceM.TraceInfo, "--> StrategyDetails.generateTwoEMATable(nShort=" + nShort + ", nLong=" + nLong + ", volT=" + volTreashold + ") ...");
            // Create DataTable and columns
            DataTable dTable = this.createTableColumns(new string[] { EMA_SHORT_COL, EMA_LONG_COL, VOLATILITY_COL });

            // Calculate specific columns
            List<double> emaShort = EMAEvaluator.calcEMAArray(this.sigQuoteM.Values, nShort);
            List<double> emaLong = EMAEvaluator.calcEMAArray(this.sigQuoteM.Values, nLong);
            List<double> vol = EMAEvaluator.calcEMAVolatilityArray(this.sigQuoteM.Values);

            Strategy strategy = new TwoEMAStrategy(nShort, nLong, volTreashold, this.startDateM, this.endDateM, this.sigQuoteM);
            PerfResult perf = this.calculatePerformance(strategy);

            // Create rows
            for (int ix = perf.StartPriceIx; ix < this.quotesM.Count; ++ix)
            {
                // Standard columns
                DataRow row = this.addStandardColumnsToRow(dTable, ix, perf.StartPriceIx, perf.Position, perf.CumulativePL, perf.DrawDown);
                // TwoEma specific
                row[EMA_SHORT_COL] = emaShort[ix];
                row[EMA_LONG_COL] = emaLong[ix];
                row[VOLATILITY_COL] = vol[ix];

                // add row to table
                dTable.Rows.Add(row);
            }

            Debug.WriteLineIf(traceM.TraceInfo, "generated " + dTable.Rows.Count + " rows and " + dTable.Columns.Count + " columns");
            return dTable;
        }

        /// <summary>Generate DataTable with single MACD strategy run</summary>
        /// <param name="nShort">Short EMA period</param>
        /// <param name="nLong">Long EMA period</param>
        /// <param name="nFast">Fast ema period (for averaging signal line)</param>
        /// <returns>DataTAble with strategy time series in columns</returns>
        public DataTable generateMACDTable(int nShort, int nLong, int nFast)
        {
            Debug.WriteIf(traceM.TraceInfo, "generateMACDTable(nShort=" + nShort + ", nLong=" + nLong + ", nFast=" + nFast + ") ...");
            // Create DataTable and columns
            DataTable dTable = this.createTableColumns(new string[] { MACD_LINE_COL , MACD_SIGNAL_COL , MACD_HISTOGRAM_COL });

            // Calculate specific columns
            List<double> macdLine;
            List<double> macdSignal;
            List<double> macdHistogram;
            MACDEvaluator.calculateMacdLines(this.sigQuoteM.Values, nShort, nLong, nFast, out macdLine, out macdSignal, out macdHistogram);

            Strategy strategy = new MACDStrategy(nShort, nLong, nFast, this.startDateM, this.endDateM, this.sigQuoteM);
            PerfResult perf = this.calculatePerformance(strategy);

            // Create rows
            for (int ix = perf.StartPriceIx; ix < this.quotesM.Count; ++ix)
            {
                // Standard columns
                DataRow row = this.addStandardColumnsToRow(dTable, ix, perf.StartPriceIx, perf.Position, perf.CumulativePL, perf.DrawDown);
                // TwoEma specific
                row[MACD_LINE_COL] = macdLine[ix];
                row[MACD_SIGNAL_COL] = macdSignal[ix];
                row[MACD_HISTOGRAM_COL] = macdHistogram[ix];

                // add row to table
                dTable.Rows.Add(row);
            }

            Debug.WriteLineIf(traceM.TraceInfo, "generated " + dTable.Rows.Count + " rows and " + dTable.Columns.Count + " columns");
            return dTable;
        }

        #region Private Methods

        /// <summary>Create all DataTable and its columns: Common (open, high, low, close, signal, volume, ..., pos, PL, drawdown. And specific columns</summary>
        /// <param name="specificCols">Array of names of strategy specific columns</param>
        private DataTable createTableColumns(string[] specificCols)
        {
            // Create DataTable
            DataTable dTable = new DataTable();
            // Create columns with their format strings
            dTable.Columns.Add(DATE_COL, typeof(DateTime)).ExtendedProperties.Add(FORMAT_EXT_PROPERTY, "d");
            dTable.Columns.Add(OPEN_COL, typeof(double)).ExtendedProperties.Add(FORMAT_EXT_PROPERTY, "N2");
            dTable.Columns.Add(HIGH_COL, typeof(double)).ExtendedProperties.Add(FORMAT_EXT_PROPERTY, "N2");
            dTable.Columns.Add(LOW_COL, typeof(double)).ExtendedProperties.Add(FORMAT_EXT_PROPERTY, "N2");
            dTable.Columns.Add(CLOSE_COL, typeof(double)).ExtendedProperties.Add(FORMAT_EXT_PROPERTY, "N2");
            if (this.sigTypeM == QuoteType.AvgHL || this.sigTypeM == QuoteType.AvgHLC)
                dTable.Columns.Add(sigTypeM.ToString(), typeof(double)).ExtendedProperties.Add(FORMAT_EXT_PROPERTY, "N2");
            dTable.Columns.Add(VOLUME_COL, typeof(double)).ExtendedProperties.Add(FORMAT_EXT_PROPERTY, "N0");

            // Specific strategy columns
            foreach (string colName in specificCols)
            {
                string format = "N2";
                if (colName == VOLATILITY_COL)
                    format = "P1";
                dTable.Columns.Add(colName, typeof(double)).ExtendedProperties.Add(FORMAT_EXT_PROPERTY, format);
            }

            // standard Performance columns
            dTable.Columns.Add(POSITION_COL, typeof(int)).ExtendedProperties.Add(FORMAT_EXT_PROPERTY, "N0");
            dTable.Columns.Add(CUMUL_PL_COL, typeof(double)).ExtendedProperties.Add(FORMAT_EXT_PROPERTY, "N2");
            dTable.Columns.Add(DRAW_DOWN_COL, typeof(double)).ExtendedProperties.Add(FORMAT_EXT_PROPERTY, "N2");
            return dTable;
        }

        /// <summary>Wrapper for calculatePerformance function results</summary>
        private struct PerfResult
        {
            private int startPriceIxM;
            private IList<int> posM;
            private List<double> cumPLM;
            private List<double> drawDownM;

            public PerfResult(int startPriceIx, IList<int> pos, List<double> cumPL, List<double> drawDownM)
            {
                this.startPriceIxM = startPriceIx;
                this.posM = pos;
                this.cumPLM = cumPL;
                this.drawDownM = drawDownM;
            }
            public IList<int> Position
            {
                get { return this.posM; }
            }
            public List<double> CumulativePL
            {
                get { return this.cumPLM; }
            }
            public List<double> DrawDown
            {
                get { return this.drawDownM; }
            }
            public int StartPriceIx
            {
                get { return this.startPriceIxM; }
            }
        }

        /// <summary>Calculate strategy performance figures. Return startPriceIx in the price List</summary>
        /// <param name="strategy">Strategy to execute</param>
        /// <param name="pos">List of positions starting form startPriceIx</param>
        /// <param name="cumPL">List of cumulative profit/loss starting from startPriceIx</param>
        /// <param name="drawDown">List of drawdowns starting from startPriceIx</param>
        /// <returns>the index of startdate in sigQuoteM (price) list</returns>
        private PerfResult calculatePerformance(Strategy strategy)
        {
            SortedList<DateTime, int> dPos = strategy.generate();
            int startPriceIx = this.sigQuoteM.IndexOfKey(this.startDateM);
            if (startPriceIx < 0)
                startPriceIx = TAAlert.CommonUtils.DateTimeUtil.ixOfLessOrEqual(this.sigQuoteM.Keys, this.startDateM);
            int endPriceIx = this.sigQuoteM.Count - 1;
            int startPosIx = 0;
            int endPosIx = dPos.Count - 1;
            Debug.Assert((endPriceIx - startPriceIx) == (endPosIx - startPosIx), "StrategyDetailsGUI: index missmatch detected");

            PerformanceCalculator calc = new PerformanceCalculator(this.closePriceM, dPos, this.transCostM);
            List<double> cumPL;
            List<double> drawDown;
            PerfStatistics stat = calc.calculateStatistics(startPriceIx, endPriceIx, startPosIx, endPosIx, out cumPL, out drawDown);

            PerfResult result = new PerfResult(startPriceIx, dPos.Values, cumPL, drawDown);
            return result;
        }

        /// <summary>Create a new row and add standard columns (open, high, low, close, volume, sigQuote, pos, cumPL, DrawDown)</summary>
        /// <param name="dTable">Table to add the row to</param>
        /// <param name="priceIx">Row's index in the sigQuoteM list</param>
        /// <param name="startPriceIx">The first index in the sigQuoteM list</param>
        /// <returns>Return the new row</returns>
        private DataRow addStandardColumnsToRow(DataTable dTable, int priceIx, int startPriceIx, IList<int> pos, List<double> cumPL, List<double> drawDown)
        {
            DataRow row = dTable.NewRow();
            DateTime date = this.quotesM.Keys[priceIx];
            row[DATE_COL] = date;
            Quote q = this.quotesM.Values[priceIx];
            row[OPEN_COL] = q.Open;
            row[HIGH_COL] = q.High;
            row[LOW_COL] = q.Low;
            row[CLOSE_COL] = q.Close;
            row[VOLUME_COL] = q.Volume;
            if (sigTypeM == QuoteType.AvgHL || sigTypeM == QuoteType.AvgHLC)
                row[sigTypeM.ToString()] = q.getValue(sigTypeM);
            row[POSITION_COL] = pos[priceIx - startPriceIx];
            row[CUMUL_PL_COL] = cumPL[priceIx - startPriceIx];
            row[DRAW_DOWN_COL] = drawDown[priceIx - startPriceIx];

            return row;
        }

        #endregion

    }
}
